package com.bood.shimmer.shiro;

import com.bood.shimmer.common.exception.UnauthorizedException;
import com.bood.shimmer.common.utils.NetUtils;
import com.bood.shimmer.common.utils.ServletUtils;
import com.bood.shimmer.common.utils.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * <p>
 * Realm 的一个配置管理类 allRealm() 方法得到所有的 realm
 * </p>
 *
 * @author：bood
 * @date：2020/9/25
 */
@Component
public class MyShiroRealm extends AuthorizingRealm {

    private static final Logger log = LoggerFactory.getLogger(MyShiroRealm.class);

    @Resource
    private JwtUtil jwtUtil;


    /**
     * <p>
     * 该方法是为了判断这个主体能否被本 Realm 处理，判断的方法是查看 token 是否为同一个类型
     * </p>
     *
     * @param authenticationToken:
     * @return：boolean
     * @author：bood
     * @date：2020/9/25
     */
    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof JwtToken;
    }

    /**
     * <p>
     * 该方法是用来添加身份信息的，比如某个用户是否具有某个操作的使用权限
     * </p>
     *
     * @param principalCollection:
     * @return：org.apache.shiro.authz.AuthorizationInfo
     * @author：bood
     * @date：2020/9/25
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        if (principalCollection == null) {
            throw new UnauthorizedException("Principal argument cannot be null!");
        }
        return new SimpleAuthorizationInfo();
    }

    /**
     * <p>
     * 在需要验证身份进行登录时，会通过这个接口，调用本方法进行审核，将身份信息返回，有误则抛出异常，在外层拦截
     * </p>
     *
     * @param authenticationToken: 这里收到的是自定义的 token 类型，在 JwtToken 中，自动向上转型。得到的 getCredentials 为 String 类型，可以使用 String.valueOf()
     * @return：org.apache.shiro.authc.AuthenticationInfo
     * @author：bood
     * @date：2020/9/25
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String jwtToken = String.valueOf(authenticationToken.getCredentials());
        // 根据 token 解析出用户信息
        String id = jwtUtil.getIdByToken(jwtToken);
        Integer ip = jwtUtil.getIpByToken(jwtToken);

        // 防止伪造 Token
        if (StringUtils.isNull(id)) {
            log.error("User message not exits,please check your token!");
            throw new AuthenticationException();
        }
        if (!ip.equals(NetUtils.ipToInteger(NetUtils.getIpAddress(ServletUtils.getRequest())))) {
            log.error("User message not exits,please check your token!");
            throw new AuthenticationException();
        }
        if (!jwtUtil.verifyToken(jwtToken)) {
            log.error("Token expired,please re-acquire!");
            throw new AuthenticationException();
        }
        // 将 principal 换为 ID
        return new SimpleAuthenticationInfo(id, jwtToken, getName());
    }

    /**
     * <p>
     * 注意坑点 : 密码校验 , 这里因为是 JWT 形式,就无需密码校验和加密,直接让其返回为 true (如果不设置的话,该值默认为 false,即始终验证不通过)
     * </p>
     *
     * @return：org.apache.shiro.authc.credential.CredentialsMatcher
     * @author：bood
     * @date：2020/9/25
     */
    @Override
    public CredentialsMatcher getCredentialsMatcher() {
        return (token, info) -> true;
    }

}